// ==++==
// 
//   Copyright (c) 2008 Microsoft Corporation. All rights reserved.
//   License: MS-Pl
//
// ==--==

using System;
using System.Collections.Generic;
using System.IO;
using Gallio.Framework.Pattern;
using Microsoft.ExtendedReflection.Metadata;
using Microsoft.ExtendedReflection.Metadata.Names;
using Microsoft.ExtendedReflection.Utilities.Safe.Diagnostics;
using Microsoft.ExtendedReflection.Collections;
using Microsoft.Pex.Engine.TestFrameworks;
using Microsoft.Pex.Engine.ComponentModel;
using MbUnit.Framework;
using Microsoft.Pex.Framework.TestFrameworks;


namespace MbUnit.Pex
{
    /// <summary>
    /// MbUnit v3 test framework implementation
    /// </summary>
    internal sealed class MbUnitTestFrameworkAttribute :
        PexTestFrameworkAttributeBase
    {
        public MbUnitTestFrameworkAttribute()
        {
            this.SetAsDefault = true;
        }

        protected override IPexTestFramework CreateTestFramework(IPexComponent host)
        {
            SafeDebug.AssumeNotNull(host, "host");

            return new TestFramework(host);
        }

        [Serializable]
        class TestFramework :
            AttributeBasedTestFrameworkBase
        {
            public TestFramework(IPexComponent host)
                : base(host)
            {
            }

            /// <summary>
            /// identify of the test framework
            /// </summary>
            /// <value></value>
            public override string Name
            {
                get { return "MbUnit"; }
            }

            readonly ShortAssemblyName frameworkAssembly = Metadata<TestFixtureAttribute>.Assembly.SerializableName;

            /// <summary>
            /// Gets the assembly name of the framework main's assembly. This name is used
            /// to automatically discover test frameworks, based the assembly references
            /// </summary>
            /// <value></value>
            public override ShortAssemblyName AssemblyName
            {
                get { return this.frameworkAssembly; }
            }

            /// <summary>
            /// Gets the root namespace.
            /// </summary>
            /// <value>The root namespace.</value>
            public override string RootNamespace
            {
                get { return "MbUnit.Framework"; }
            }

            /// <summary>
            /// Hint on the location of the test framework assembly
            /// </summary>
            /// <param name="assemblyFileName">Name of the assembly file.</param>
            /// <returns></returns>
            protected override string GetHintPath(string assemblyFileName)
            {
                SafeDebug.AssumeNotNullOrEmpty(assemblyFileName, "assemblyFileName");

                string directory =
                    Environment.ExpandEnvironmentVariables(@"%ProgramFiles%\MbUnit v3");
                if (Directory.Exists(directory))
                    return Path.Combine(
                        directory,
                        assemblyFileName
                        );
                else
                    return null;
            }

            /// <summary>
            /// Gets a value indicating whether
            /// partial test classes
            /// </summary>
            /// <value></value>
            public override bool SupportsPartialClasses
            {
                get { return true; }
            }

            //static TypeName assemblyCleanupAttribute = frameworkClass("AssemblyCleanupAttribute");
            ///// <summary>
            ///// Tries to get the assembly set up tear down attribute.
            ///// </summary>
            ///// <param name="assembly">The assembly.</param>
            ///// <param name="setUp">The set up.</param>
            ///// <param name="tearDown">The tear down.</param>
            ///// <returns></returns>
            //public override bool TryGetAssemblySetupTeardownMethods(
            //    AssemblyEx assembly,
            //    out Method setUp,
            //    out Method tearDown)
            //{
            //    setUp = null;
            //    tearDown = null;
            //    object assemblyCleanUp = AttributeHelper.GetAttribute(
            //        assembly,
            //        assemblyCleanupAttribute);
            //    if (assemblyCleanUp == null)
            //    {
            //        return false;
            //    }
            //    else
            //    {
            //        // get the assembly cleanup type from reflection
            //        Type targetType = 
            //            (Type)assemblyCleanUp.GetType().GetProperty("TargetType").GetValue(assemblyCleanUp, null);
            //        if (targetType != null)
            //        {
            //            TypeDefinition targetTypeDefinition = MetadataFromReflection.GetTypeDefinition(targetType);
            //            if (targetTypeDefinition.GenericTypeParameters.Length == 0)
            //                foreach (MethodDefinition methodDefinition in targetTypeDefinition.DeclaredStaticMethods)
            //                    if (!methodDefinition.IsConstructor &&
            //                        methodDefinition.IsVisible(VisibilityContext.Exported) &&
            //                        methodDefinition.GenericMethodParameters.Length == 0)
            //                    {
            //                        if (AttributeHelper.IsDefined(methodDefinition, this.SetUpAttribute))
            //                            setUp = methodDefinition.Instantiate(TypeEx.NoTypes, TypeEx.NoTypes);
            //                        if (AttributeHelper.IsDefined(methodDefinition, this.TearDownAttribute))
            //                            tearDown = methodDefinition.Instantiate(TypeEx.NoTypes, TypeEx.NoTypes);
            //                        if (setUp != null && tearDown != null)
            //                            break;
            //                    }
            //        }
            //        return setUp != null || tearDown != null;
            //    }
            //}

            public override bool TryGetAssemblySetupTeardownMethods(
                AssemblyEx assembly,
                out Method setup,
                out Method teardown)
            {
                setup = teardown = null;
                return false;
            }

            /// <summary>
            /// Gets a value indicating whether[fixture set up tear down are instance methods.
            /// </summary>
            /// <value>
            /// 	<c>true</c> if [fixture set up tear down instance]; otherwise, <c>false</c>.
            /// </value>
            public override bool FixtureSetUpTearDownInstance
            {
                get { return true; }
            }

            /// <summary>
            /// Gets the name of the fixture attribute
            /// </summary>
            /// <value>The fixture attribute.</value>
            public override TypeName FixtureAttribute
            {
                get { return Metadata<TestFixtureAttribute>.SerializableName; }
            }

            /// <summary>
            /// Determines whether the specified target is fixture.
            /// </summary>
            /// <param name="target">The target.</param>
            /// <returns>
            /// 	<c>true</c> if the specified target is fixture; otherwise, <c>false</c>.
            /// </returns>
            public override bool IsFixture(TypeDefinition target)
            {
                return AttributeHelper.IsDefined(
                    target,
                    Metadata<TestTypePatternAttribute>.SerializableName,
                    true);
            }

            /// <summary>
            /// Determines whether the specified method is a test.
            /// </summary>
            /// <param name="method">The method.</param>
            /// <returns>
            /// 	<c>true</c> if the specified method is test; otherwise, <c>false</c>.
            /// </returns>
            public override bool IsTest(MethodDefinition method)
            {
                return AttributeHelper.IsDefined(
                    method,
                    Metadata<TestMethodPatternAttribute>.SerializableName,
                    true);
            }

            /// <summary>
            /// Gets the name of the fixture setup attribute
            /// </summary>
            /// <value>The fixture set up attribute.</value>
            public override TypeName FixtureSetUpAttribute
            {
                get { return Metadata<FixtureSetUpAttribute>.SerializableName; }
            }

            /// <summary>
            /// Gets the ExpectedException attribute.
            /// </summary>
            /// <value>The expected exception attribute.</value>
            public override TypeName ExpectedExceptionAttribute
            {
                get { return Metadata<ExpectedExceptionAttribute>.SerializableName; }
            }

            /// <summary>
            /// Gets the name of the fixture teardown attribute
            /// </summary>
            /// <value>The fixture tear down attribute.</value>
            public override TypeName FixtureTearDownAttribute
            {
                get { return Metadata<FixtureTearDownAttribute>.SerializableName; }
            }

            /// <summary>
            /// Gets the name of the test setup attribute.
            /// </summary>
            /// <value>The set up attribute.</value>
            public override TypeName SetUpAttribute
            {
                get { return Metadata<SetUpAttribute>.SerializableName; }
            }

            /// <summary>
            /// Gets the name of the test attribute.
            /// </summary>
            /// <value>The set up attribute.</value>
            public override TypeName TestAttribute
            {
                get { return Metadata<TestAttribute>.SerializableName; }
            }

            /// <summary>
            /// Gets the name of the test teardown attribute.
            /// </summary>
            /// <value>The tear down attribute.</value>
            public override TypeName TearDownAttribute
            {
                get { return Metadata<TearDownAttribute>.SerializableName; }
            }

            /// <summary>
            /// Gets the ignore attribute.
            /// </summary>
            /// <value>The ignore attribute.</value>
            public override TypeName IgnoreAttribute
            {
                get { return Metadata<IgnoreAttribute>.SerializableName; }
            }

            /// <summary>
            /// Whether the ignore attribute constructor takes a message as its first argument.
            /// </summary>
            /// <value></value>
            protected override bool HasIgnoreAttributeMessage
            {
                get { return true; }
            }

            /// <summary>
            /// Gets the ignore message property.
            /// </summary>
            /// <value>The ignore message property.</value>
            protected override string IgnoreMessageProperty
            {
                get { return "Reason"; }
            }

            /// <summary>
            /// Gets the expected exception property name.
            /// </summary>
            /// <value>The expected exception property.</value>
            protected override string ExpectedExceptionProperty
            {
                get { return "ExceptionType"; }
            }

            /// <summary>
            /// Gets a list of attribute that should be duplicated from the
            /// pex test to the paramterized test
            /// </summary>
            /// <returns></returns>
            protected override IEnumerable<TypeName> GetSatelliteAttributeTypes()
            {
                return Indexable.Empty<TypeName>();
            }

            /// <summary>
            /// Tries the get categories.
            /// </summary>
            /// <param name="element">The element.</param>
            /// <param name="names">The names.</param>
            /// <returns></returns>
            protected override bool TryGetCategories(ICustomAttributeProviderEx element, out IEnumerable<string> names)
            {
                names = null;
                return false;
            }

            /// <summary>
            /// Gets the type of the assertion exception.
            /// </summary>
            /// <value>The type of the assertion exception.</value>
            public override TypeName AssertionExceptionType
            {
                get { return Metadata<AssertionException>.SerializableName; }
            }
        }
    }
}
